IDA Pro权威指南-第一章 反汇编简介

反汇编简介

反汇编理论

编程语言分为好几代:

  1. 第一代语言:这些语言是最低级的语言,一般由0或1或某些简写编码(如十六进制码)组成。第一代语言称为机器语言,有时也叫做字节码,而机器语言程序常被称为二进制文件。
  2. 第二代语言:汇编语言。汇编语言会将具体的位模式或操作码,与短小且易于记忆的字符序列(即助记符)对应起来。
  3. 第三代语言:高级程序语言,即C、Java等。程序员通常使用编译器将程序转换成汇编语言,或者直接转换为机器语言(或某种大致的等价形式,如字节码)。

何为反汇编

程序员使用编译器、汇编器和链接器中的一个或几个创建可执行程序。由源代码(c语言程序)到汇编语言再到机器码的过程。为了回溯编程过程(或对程序进行逆向工程),我们使用各种工具来撤销汇编和编译过程,这些工具就叫做反汇编器和反编译器。反汇编器的输入的汇编语言,输出是汇编语言形式,反编译器是以汇编语言甚至机器语言为输入,其输出结果是高级语言。

在竞争激烈的软件市场,“恢复源代码”的前景总是充满吸引力的。以下列举几个原因,说明反汇编困难重重:

  1. 编译过程会造成损失。机器语言中没有变量或函数名,变量类型信息只有通过数据的用途来确定。
  2. 编译属于多对多操作。源程序可以通过许多不同的方式转换成汇编语言,而机器语言也可以通过许多不同方式转换成源程序。
  3. 反编译器非常依赖于语言和库。
  4. 要想准确的反编译一个二进制文件,需要近乎完美的反汇编能力。

为何反汇编

通常,使用反汇编工具是为了在没有源代码的情况下促进对程序的了解。需要进行反汇编的常见情况包括以下几种:

  1. 分析恶意软件
  2. 分析闭源软件的漏洞
  3. 分析闭源软件的互操作性
  4. 分析编译器生成的代码,以验证编译器的性能和准确性
  5. 在调试时显示程序指令
  1. 分析恶意软件
    恶意软件的作者很少会提供他们“作品”的源代码,除非你对付的是一种基于脚本的蠕虫。由于缺乏源代码,要准确的了解恶意代码的运行机制,选择非常有限。动态分析和静态分析是分析恶意软件的两种主要技术。动态分析是指在严格控制的环境(沙盒)中执行恶意软件,并使用系统检测实用工具记录其所有行为。相反,静态分析则试图通过浏览程序代码来理解程序的行为。此时,要查看的就是对恶意软件进行反汇编之后的代码。
  2. 漏洞分析
    将整个安全审核过程划分为3个步骤:发现漏洞、分析漏洞、开发破解程序。第一个步骤是发现程序中潜在的可供利用的条件,一般情况下,可以通过模糊测试等动态技术来达到这一目的,也可通过静态分析来实现。编译器究竟如何分配程序变量,反汇编代码清单提供了详细的信息。另外,要了解编译器到底如何对全局声明或在函数中声明的所有变量进行排序,查看反汇编代码清单是唯一的办法。在开发破解程序时,了解变量之间的这些空间关系往往非常重要。
  3. 软件互操作性
    如果仅以二进制形式发布一个软件,竞争对手想要创建可以和它互操作的软件,或者为该软件提供插件,将会非常困难。针对某个仅有一种平台支持的硬件而发布的驱动程序代码,就是一个常见的例子。如果厂商暂时不支持,或者更糟糕的,拒绝支持在其他平台上使用它们的硬件,那么为了开发支持硬件的软件驱动程序,可能需要完成大量的逆向工作。在这些情况下,静态代码分析几乎是唯一的补救方法。
  4. 编译器验证
    由于编译器(或汇编器)的用途是生成机器语言,因此优秀的反汇编工具通常需要验证编译器是否符合设计规范。除准确性外,分析人员还可以从中寻找优化编译器输出的机会,查知编译器本身是否容易被攻破,以致于可以在生成的代码中插入后门,等等。
  5. 显示调试信息
    在调试器中生成代码清单,可能是反汇编器最常见的一种途径。在调试过程中,为了了解详细的环境和背景信息,最好是结合使用调试器和优秀的反汇编器。

如何反汇编

以上已经知道了反汇编的目的,下面是介绍如何反汇编。
以反汇编器所面临的一个艰巨任务为例:对于一个100KB的文件,请区分其中的代码与数据,并把代码转换成汇编语言显示给用户,在整个过程中,不要遗漏任何信息。要求反汇编器定位函数,识别跳转表并确定局部变量,这进一步增加了反汇编器工作的难度。
为了满足所有要求,反汇编器必须从大量算法中选择一些适当的算法,来处理我们提供的文件。

  1. 基本的反汇编算法
    首先开发一个以机器语言为输入、以汇编语言为输出的简单算法。
  • 第一步。确定进行反汇编的代码区域。通常,指令与数据混杂在一起,区分它们就显得非常重要。以最常见的情形——反汇编可执行文件为例,该文件必须符合可执行文件的某种通用格式,如Windows所用的可移植可执行(PE)格式或许多UNIX系统常用的可执行和链接格式(ELF)。这些格式通常含有一种机制,用来确定文件中包含代码和代码入口点的部分的位置(通常变现为层级文件头的形式)。
  • 第二步。知道指令的起始地址后,下一步就是读取该地址(或文件偏移量)所包含的值,并执行一次表查找,将二进制操作码的值与它的汇编语言助记符对应起来。
  • 第三步。获取指令并解码任何所需的操作数后,需要对它的汇编语言等价形式进行格式化,并将其在反汇编代码中输出。
  • 第四步。输出一条指令后,继续反汇编下一条指令,并重复上述过程,直到反汇编完文件中的所有指令。

有大量算法可以用于确定从何处开始反汇编、如何选择下一条反汇编的指令、如何区分代码与数据,以及如何确定何时完成对最后一条指令的反汇编。线性扫描和递归下降是两种最主要的反汇编算法。

  1. 线性扫描反汇编
    线性扫描反汇编算法采用一种非常直接的方法来确定反汇编的指令的位置:一条指令结束、另一条指令开始的地方。确定起始位置的常用解决办法是,假设程序中标注为代码(通常由程序文件的头部指定)的节所包含的全部是机器语言指令。反汇编从一个代码段的第一个字节开始,以线性模式扫描整个代码段,逐条反汇编每条指令,直到完成整个代码段。这种算法并不会通过识别分支等非线性指令来了解程序的控制流。

线性扫描算法的主要优点在于:它能够完全覆盖程序的所有代码段。主要缺点是:没有考虑到代码中可能混有数据。

GNU调试器(gdb)、微软公司的WinDug调试器和objdump实用工具的反汇编引擎均采用线性扫描算法。

  1. 递归下降反汇编
    递归下降采用另外一种不同的方法来定位指令。递归下降算法重视控制流的概念。控制流根据一条指令是否被另一条引用来决定是否对其进行反汇编。为了便于理解递归下降,根据指令对CPU指令指针的影响对他们进行分类。
  1. 顺序流指令
    顺序流指令将执行权传递给紧随其后的下一条指令。如add、mov、push和pop。
  2. 条件分支指令
    条件分支指令提供两条可能的执行路径。如果条件为真,则执行分支,并且必须修改指令指针,使其指向分支的目标。但是,如果条件为假,则继续以线性模式执行指令,并使用线性扫描方法反汇编下一条指令。
  3. 无条件分支指令
    无条件分支并不遵循线性流模式。与顺序流指令一样,执行权只能传递给一条指令,但那条指令不需要紧接在分支指令后面。
  4. 函数调用指令
    函数调用指令的运行方式与无条件跳转指令非常相似,唯一不同在于,一旦函数完成,执行权将返回给紧跟在调用指令后面的指令。从被调用函数返回时,如果程序的运行出现异常,递归下降就有可能失败。
    5.返回指令
    有时,递归下降算法访问了所有路径,而且,函数返回指令没有提供接下来要执行的指令的信息。这时,递归下降反汇编器会转而处理前面搁置在一旁的延迟反汇编地址列表。反汇编器从这个列表中取出一个地址,并从这个地址开始继续反汇编过程。

递归下降算法的一个主要优点在于:它具有区分代码与数据的强大能力。主要缺点是:它无法处理间接代码路径,例如利用指针表来查找目标地址的跳转和调用。
IDA Pro 是一种典型的递归下降反汇编器。

文章目录
  1. 1. 反汇编简介
    1. 1.1. 反汇编理论
    2. 1.2. 何为反汇编
    3. 1.3. 为何反汇编
    4. 1.4. 如何反汇编